package com.thm.tfast.modules.sys.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.thm.tfast.core.enums.DeleteFlagEnum;
import com.thm.tfast.core.enums.StatusEnum;
import com.thm.tfast.core.exception.Asserts;
import com.thm.tfast.core.sevruity.domain.SysUserDetails;
import com.thm.tfast.core.utils.JwtTokenUtil;
import com.thm.tfast.core.utils.SecurityUtil;
import com.thm.tfast.modules.sys.mapper.SysUserMapper;
import com.thm.tfast.modules.sys.model.SysLog;
import com.thm.tfast.modules.sys.model.SysUser;
import com.thm.tfast.modules.sys.model.SysUserRole;
import com.thm.tfast.modules.sys.model.dto.user.*;
import com.thm.tfast.modules.sys.model.vo.menu.MenuVo;
import com.thm.tfast.modules.sys.model.vo.permission.PermissionVo;
import com.thm.tfast.modules.sys.model.vo.role.RoleVo;
import com.thm.tfast.modules.sys.model.vo.user.CaptchaVo;
import com.thm.tfast.modules.sys.model.vo.user.UserInfoVo;
import com.thm.tfast.modules.sys.service.*;
import com.wf.captcha.SpecCaptcha;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户信息 服务实现类
 * </p>
 *
 * @author THM
 * @since 2022-10-10
 */
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
    private static final Logger LOGGER = LoggerFactory.getLogger(SysUserServiceImpl.class);
    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Value("${default.password}")
    private String defaultPassword;

    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private JwtTokenUtil jwtTokenUtil;
    @Resource
    private SysCacheService sysCacheService;
    @Resource
    private SysRoleService sysRoleService;
    @Resource
    private SysPermissionService sysPermissionService;
    @Resource
    private SysMenuService sysMenuService;
    @Resource
    private SysUserRoleService sysUserRoleService;
    @Resource
    private SysLogService sysLogService;
    @Override
    public SysUser getUserByUsername(String username) {
        SysUser sysUser = sysCacheService.getUser(username);
        if (Objects.isNull(sysUser)){
            sysUser = this.getOne(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getUserName, username));
            if (Objects.nonNull(sysUser))
                sysCacheService.setUser(sysUser);
        }
        return sysUser;
    }

    @Override
    public SysUserDetails loadUserByUsername(String username) {
        //获取用户信息
        SysUser sysUser = getUserByUsername(username);
        if (Objects.nonNull(sysUser)) {
            List<PermissionVo> permissions = sysCacheService.getPermission(sysUser.getId());
            if (CollectionUtil.isEmpty(permissions)){
                permissions = sysPermissionService.getPermissionByUserId(sysUser.getId());
                sysCacheService.setPermission(sysUser.getId(),permissions);
            }
            return new SysUserDetails(sysUser, permissions);
        }
        throw new UsernameNotFoundException("用户名或密码错误");
    }

    @Override
    public CaptchaVo getCaptcha() {
        SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
        String verCode  = specCaptcha.text();
        Long key = IdUtil.getSnowflakeNextId();
        //存入 redis 有效期5分钟
        sysCacheService.setCode(key,verCode);
        CaptchaVo vo = new CaptchaVo();
        vo.setKey(key);
        vo.setImage(specCaptcha.toBase64());
        return vo;
    }

    @Override
    public String login(UserLoginDto dto) {
        StopWatch stopWatch = StopWatch.create(dto.getUsername());
        stopWatch.start();
        if (Objects.isNull(dto.getKey())){
            Asserts.fail("验证码Key不能为空");
        }
        if (Objects.isNull(dto.getVerCode())){
            Asserts.fail("验证码不能为空");
        }
        String verCode = sysCacheService.getCode(dto.getKey());
        if (Objects.isNull(verCode)){
            Asserts.fail("未找到验证码信息");
        }
        if (!Objects.equals(verCode.toLowerCase(),dto.getVerCode().toLowerCase())) {
            Asserts.fail("验证码不正确");
        }
        sysCacheService.delCode(dto.getKey());
        String token = null;
        //密码需要客户端加密后传递
        try {
            SysUserDetails sysUserDetails = loadUserByUsername(dto.getUsername());
            if (!passwordEncoder.matches(dto.getPassword(), sysUserDetails.getPassword())) {
                Asserts.fail("用户名或密码错误");
            }
            if (!sysUserDetails.isEnabled()) {
                Asserts.fail("帐号已被禁用");
            }
            //设置当前登录用户信息
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(sysUserDetails,
                    null,
                    sysUserDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
            //生成token
            token = jwtTokenUtil.generateToken(sysUserDetails);
            //创建缓存
            createCache(token, sysUserDetails);
            //修改登录时间
            updateLoginTimeByUsername(sysUserDetails.getSysUser());
            stopWatch.stop();
            //创建登录日志
            createLog(dto, sysUserDetails.getUserId(), stopWatch.getTotalTimeMillis());
        } catch (AuthenticationException e) {
            LOGGER.warn("登录异常:{}", e.getMessage());
        }
        return token;
    }

    /**
     * 创建缓存
     * @param token token
     * @param sysUserDetails 用户信息
     */
    private void createCache(String token, SysUserDetails sysUserDetails) {
        //缓存token
        sysCacheService.setToken(sysUserDetails.getUsername(), token);
        //缓存角色
        List<RoleVo> roleList = sysRoleService.getRolesByUserId(sysUserDetails.getUserId());
        if (CollectionUtil.isNotEmpty(roleList)) {
            sysCacheService.setRole(sysUserDetails.getUserId(), roleList);
        }
        //缓存菜单
        List<MenuVo> menuList = sysMenuService.getMenusByUserId(sysUserDetails.getUserId());
        if (CollectionUtil.isNotEmpty(menuList)) {
            sysCacheService.setMenu(sysUserDetails.getUserId(), menuList);
        }
    }

    /**
     * 根据用户名修改登录时间
     */
    private void updateLoginTimeByUsername(SysUser sysUser) {
        sysUser.setLoginTime(new Date());
        this.updateById(sysUser);
    }

    private void createLog(UserLoginDto dto, Long userId, Long time){
        SysLog sysLog = sysLogService.createSysLog(List.of(dto), userId);
        sysLog.setTitle("用户登录");
        sysLog.setTime(time);
        sysLogService.save(sysLog);

    }

    @Override
    public void logout(String username) {
        sysCacheService.delToken(username);
        sysCacheService.delUser(username);
    }

    @Override
    public String refreshToken(String oldToken) {
        oldToken = oldToken.substring(this.tokenHead.length());
        String userName = jwtTokenUtil.getUserNameFromToken(oldToken);
        String refreshHeadToken = jwtTokenUtil.refreshHeadToken(oldToken);
        if (StrUtil.isNotEmpty(refreshHeadToken)) {
            sysCacheService.setToken(userName,refreshHeadToken);
        }
        return refreshHeadToken;
    }

    @Override
    public UserInfoVo getUserInfo() {
        UserInfoVo userInfoVo = new UserInfoVo();
        SysUserDetails currentUser = SecurityUtil.getCurrentUser();
        userInfoVo.setUserName(currentUser.getUsername());
        userInfoVo.setIcon(currentUser.getSysUser().getIcon());
        List<RoleVo> roleList = sysCacheService.getRole(currentUser.getUserId());
        if (CollectionUtil.isEmpty(roleList)) {
            roleList = sysRoleService.getRolesByUserId(currentUser.getUserId());
            sysCacheService.setRole(currentUser.getUserId(), roleList);
        }
        userInfoVo.setRoles(roleList.stream().map(RoleVo::getName).collect(Collectors.toList()));
        List<MenuVo> menuList = sysCacheService.getMenu(currentUser.getUserId());
        if (CollectionUtil.isEmpty(menuList)) {
            menuList = sysMenuService.getMenusByUserId(currentUser.getUserId());
            sysCacheService.setMenu(currentUser.getUserId(), menuList);
        }
        userInfoVo.setMenus(menuList);
        return userInfoVo;
    }

    @Override
    public Page<SysUser> page(String keyword, Integer pageNum, Integer pageSize) {
        Page<SysUser> page = new Page<>(pageNum, pageSize);
        LambdaQueryWrapper<SysUser> wrapper = Wrappers.lambdaQuery();
        if (StrUtil.isNotEmpty(keyword)) {
            wrapper
                    .like(SysUser::getUserName, keyword)
                    .or()
                    .like(SysUser::getNickName, keyword);
        }
        return this.page(page, wrapper);
    }

    @Override
    @Transactional
    public Long insert(UserInsertDto dto) {
        SysUser sysUser = BeanUtil.toBean(dto, SysUser.class);
        SysUser userByUsername = this.getOne(Wrappers.<SysUser>lambdaQuery()
                .eq(SysUser::getUserName, sysUser.getUserName()));
        if (Objects.nonNull(userByUsername)){
            Asserts.fail("用户名已存在");
        }
        SysUserDetails currentUser = SecurityUtil.getCurrentUser();
        String password = sysUser.getPassword();
        if (StrUtil.isNotEmpty(password)){
            sysUser.setPassword(passwordEncoder.encode(password));
        } else {
            //添加默认密码
            sysUser.setPassword(passwordEncoder.encode(defaultPassword));
        }
        sysUser.setCreateUser(currentUser.getUserId());
        sysUser.setCreateTime(currentUser.getDate());
        sysUser.setUpdateUser(currentUser.getUserId());
        sysUser.setUpdateTime(currentUser.getDate());
        this.save(sysUser);
        return sysUser.getId();
    }

    @Override
    @Transactional
    public boolean update(UserUpdateDto dto) {
        if (Objects.isNull(this.getById(dto.getId()))){
            Asserts.fail("用户信息不存在");
        }
        SysUser sysUser = BeanUtil.toBean(dto, SysUser.class);
        SysUserDetails currentUser = SecurityUtil.getCurrentUser();
        sysUser.setUpdateUser(currentUser.getUserId());
        sysUser.setUpdateTime(currentUser.getDate());
        //删除用户缓存
        sysCacheService.delUser(sysUser.getUserName());
        return this.updateById(sysUser);
    }

    @Override
    @Transactional
    public boolean enableOrDisable(Long userId) {
        SysUser sysUser = this.getById(userId);
        if (Objects.isNull(sysUser)){
            Asserts.fail("用户信息不存在");
        }
        SysUserDetails currentUser = SecurityUtil.getCurrentUser();
        Integer status;
        if (Objects.equals(sysUser.getStatus(), StatusEnum.ENABLE.getCode())) {
            //禁用
            status = StatusEnum.DISABLE.getCode();
        } else {
            //启用
            status = StatusEnum.ENABLE.getCode();
        }
        sysUser.setStatus(status);
        sysUser.setUpdateUser(currentUser.getUserId());
        sysUser.setUpdateTime(currentUser.getDate());
        //删除用户缓存
        sysCacheService.delUser(sysUser.getUserName());
        sysCacheService.delToken(sysUser.getUserName());
        return this.updateById(sysUser);
    }

    @Override
    @Transactional
    public boolean resetPassword(Long userId) {
        SysUser sysUser = this.getById(userId);
        if (Objects.isNull(sysUser)){
            Asserts.fail("用户信息不存在");
        }
        SysUserDetails currentUser = SecurityUtil.getCurrentUser();
        sysUser.setPassword(passwordEncoder.encode(DigestUtil.sha256Hex(defaultPassword)));
        sysUser.setUpdateUser(currentUser.getUserId());
        sysUser.setUpdateTime(currentUser.getDate());
        //删除用户缓存
        sysCacheService.delUser(sysUser.getUserName());
        sysCacheService.delToken(sysUser.getUserName());
        return this.updateById(sysUser);
    }

    @Override
    @Transactional
    public boolean updatePassword(UserPasswordDto dto) {
        SysUser sysUser = this.getById(dto.getUserId());
        if (Objects.isNull(sysUser)){
            Asserts.fail("用户信息不存在");
        }
        if (!passwordEncoder.matches(dto.getOldPassword(), sysUser.getPassword())) {
            Asserts.fail("原密码错误");
        }
        if (Objects.equals(dto.getOldPassword(),dto.getNewPassword())){
            Asserts.fail("原密码不能与新密码相同");
        }
        sysUser.setPassword(passwordEncoder.encode(DigestUtil.sha256Hex(dto.getNewPassword())));
        sysUser.setUpdateUser(dto.getUserId());
        sysUser.setUpdateTime(new Date());
        sysCacheService.delUser(sysUser.getUserName());
        sysCacheService.delToken(sysUser.getUserName());
        return this.updateById(sysUser);
    }

    @Override
    @Transactional
    public boolean delete(Long userId) {
        SysUser sysUser = this.getById(userId);
        if (Objects.isNull(sysUser)){
            Asserts.fail("用户信息不存在");
        }
        SysUserDetails currentUser = SecurityUtil.getCurrentUser();
        sysUser.setUpdateUser(currentUser.getUserId());
        sysUser.setUpdateTime(currentUser.getDate());
        sysUser.setDelFlag(DeleteFlagEnum.DELETED.getCode());
        //删除用户缓存
        sysCacheService.delToken(sysUser.getUserName());
        sysCacheService.delUser(sysUser.getUserName());
        sysCacheService.delRole(userId);
        sysCacheService.delMenu(userId);
        sysCacheService.delPermission(userId);
        //物理删除用户
        return this.updateById(sysUser);
    }

    @Override
    @Transactional
    public boolean allotRole(UserRoleDto dto) {
        if (Objects.isNull(this.getById(dto.getUserId()))){
            Asserts.fail("用户信息不存在");
        }
        //删除用户角色
        sysUserRoleService.remove(Wrappers.<SysUserRole>lambdaQuery().eq(SysUserRole::getUserId, dto.getUserId()));
        //删除用户角色缓存
        sysCacheService.delRole(dto.getUserId());
        sysCacheService.delMenu(dto.getUserId());
        sysCacheService.delPermission(dto.getUserId());
        //新增用户角色
        List<SysUserRole> sysUserRoles = dto.getRoleIds().stream().map(roleId -> {
            SysUserRole sysUserRole = new SysUserRole();
            sysUserRole.setUserId(dto.getUserId());
            sysUserRole.setRoleId(roleId);
            return sysUserRole;
        }).collect(Collectors.toList());

        return sysUserRoleService.saveBatch(sysUserRoles);
    }
}
